﻿using System;
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Remoting.Messaging;
using Random = UnityEngine.Random;

public class AStar : MonoBehaviour
{
    [Header("小方格预设体")]
    public GameObject cubePrefab;
    [Header("小方格的边长(含间隙)")]
    public float cubeLength = 0.5f;
    [Header("地面缩放对应的实际长度")]
    public int gridScale = 10;
    [Header("地形网格的长度")]
    public int gridLength;
    [Header("地形网格的宽度")]
    public int gridWidth;
    [Range(0,100)]
    [Header("障碍物出现的比例")]
    public int obstacleScale = 30;
    [Space]
    [Header("起点坐标")]
    public int startX = 0;
    public int startY = 0;
    [Header("终点坐标")]
    public int endX = 0;
    public int endY = 0;

    //存储所有的格子
    private GridItem[,] _gridItems;

    //路径总长度
    private int pathCount;

    private void Start()
    {
        //初始化格子
        GridInit();
        //寻路
        AStarPathFinding();
    }

    /// <summary>
    /// 地形网格的初始化
    /// </summary>
    private void GridInit()
    {
        //计算网格的长度(可存储方格的数量)
        gridLength = (int)(transform.localScale.x * gridScale/cubeLength);
        //计算网格的宽度(可存储方格的数量)
        gridWidth = (int)(transform.localScale.z * gridScale/cubeLength);
        //按照生成网格数量初始化数组
        _gridItems = new GridItem[gridLength,gridWidth];
        //生成所有格子
        for (int i = 0; i < gridLength; i++)
        {
            for (int j = 0; j < gridWidth; j++)
            {
                //地形偏移量
                Vector3 gridOffset = new Vector3(-gridScale / 2 * transform.localScale.x, 0,
                    -gridScale / 2 * transform.localScale.z);
                //小方块偏移量
                Vector3 cubeOffset = new Vector3(cubeLength / 2, 0,
                    cubeLength / 2);
                //生成小方格
                GameObject cube = Instantiate(cubePrefab,
                    new Vector3((float) i * cubeLength, 0,
                        (float) j * cubeLength) + 
                    gridOffset/*地形偏移量*/ + cubeOffset/*方块偏移量*/ + transform.position,/*地形坐标*/
                    Quaternion.identity);
                //设置父物体为Panel
                cube.transform.SetParent(transform);
                //获取脚本组件GridItem
                GridItem item = cube.GetComponent<GridItem>();
                //存储格子脚本到数组
                _gridItems[i, j] = item;
                //设置Item的坐标
                item.x = i;
                item.y = j;
                //随机一个数字
                int ran = Random.Range(1, 101);
                //如果在范围内
                if (ran <= obstacleScale)
                {
                    //设置为障碍物类型
                    item.SetItemType(ItemType.Obstacle);
                }
            }
        }
        //设置起点和终点格子(覆盖障碍物格子也无所谓)
        try
        {
            _gridItems[startX,startY].SetItemType(ItemType.Start);
            _gridItems[endX,endY].SetItemType(ItemType.End);
        }
        //如果起点或终点越界 则都设置为默认值
        catch (IndexOutOfRangeException e)
        {
            startX = 0;
            startY = 0;
            endX = gridLength - 1;
            endY = gridWidth - 1;
            _gridItems[startX,startY].SetItemType(ItemType.Start);
            _gridItems[endX,endY].SetItemType(ItemType.End);
            Debug.LogWarning("起点坐标或终点坐标设置错误");
        }
    }

   

    #region A Star Need Field

    //开放列表-->存储所有FGH待计算格子
    private List<GridItem> openList;
    //关闭列表-->存储所有发现者格子(Parent)
    private List<GridItem> closeList;
    //路径栈
    private Stack<GridItem> pathStack;
    #endregion
    
    /// <summary>
    /// A*寻路(目的：通过找周围的八个格子，筛选符合要求的格子来找下一下中心点，以此类推，直到找到的下一个中心点就是中点，路径便生成了)
    /// </summary>
    private void AStarPathFinding()
    {
        //初始化列表
        openList = new List<GridItem>();
        closeList = new List<GridItem>();
        pathStack = new Stack<GridItem>();
        //将起点放置到开启列表
        openList.Add(_gridItems[startX,startY]);
        //开启循环
        while (true)
        {
            //按照F值从小到大排序
            openList.Sort();
            //找到F值最小的格子
            GridItem center = openList[0];
            //如果当前中心格子是终点
            if (center.itemType == ItemType.End)
            {
                //TODO:回溯找到导航路径
                GeneratePath(center);
                break;
            }
            //以center格子为中心，去发现它周边的8个格子
            for (int i = -1; i <= 1; i++)
            {
                for (int j = -1; j <= 1; j++)
                {
                    //如果是中心格子，略过
                    if (i == 0 && j == 0) 
                        continue;
                    //真正发现的格子的坐标为
                    int x = center.x + i;
                    int y = center.y + j;
                    //判断下标是否越界，略过
                    if(x < 0 || x > gridLength - 1)
                        continue;
                    if(y < 0 || y > gridWidth - 1)
                        continue;
                    //临时存储当前的格子
                    GridItem crtItem = _gridItems[x, y];
                    //判断格子是否是障碍物，略过
                    if(crtItem.itemType == ItemType.Obstacle)
                        continue;
                    //如果该格子已经作为中心被计算过了，略过
                    if(closeList.Contains(crtItem))
                        continue;
                    
                    //计算G值
                    int G = CountOffsetG(i, j) + center.G;
                    //如果该格子从未计算过G值，或原G值比新算的G值要大
                    if (crtItem.G == 0 || crtItem.G > G)
                    {
                        //更新G值
                        crtItem.G = G;
                        //更新发现者
                        crtItem.parent = center;
                        //计算H值
                        int H = CountH(x, y);
                        //赋值给格子
                        crtItem.H = H;
                        //计算F值
                        crtItem.F = crtItem.G + crtItem.H;
                    }
                    //经过重重筛选
                    //将当前格子添加到openList
                    if (!openList.Contains(crtItem))
                    {
                        openList.Add(crtItem);
                    }
                }
            }
            //for循环结束
            //将当前中心，从openList中移除
            openList.Remove(center);
            //将当前中心，添加到closeList
            closeList.Add(center);
            //遍历到了尽头，还是没有找到终点，说明无法找到终点，死胡同
            if (openList.Count == 0)
            {
                //TODO:无法找到路径
                Debug.Log("无法找到路径~~~!!!");
                break;
            }
        }
    }

    /// <summary>
    /// 生成路径
    /// </summary>
    /// <param name="item"></param>
    private void GeneratePath(GridItem item)
    {
        //把当前元素入栈
        pathStack.Push(item);
        //判断当前元素是否有发现者
        if (item.parent != null)
        {
            //递归查找
            GeneratePath(item.parent);
        }
        else
        {
            //找到起点了，路径点存储完毕
            //生成路径
            pathCount = pathStack.Count;
            //开启协程，渲染路径 
            StartCoroutine(ShowPath());
        }
    }

    IEnumerator ShowPath()
    {
        while (pathStack.Count > 0)
        {
            yield return new WaitForSeconds(0.2f);
            //出栈
            GridItem item = pathStack.Pop();
            if (item.itemType == ItemType.Normal)
            {
                item.SetColor(Color.Lerp(Color.red, Color.green, (pathCount-pathStack.Count)/(float)pathCount));
            }
        }
    }

    /// <summary>
    /// 计算H值
    /// </summary>
    /// <param name="x"></param>
    /// <param name="y"></param>
    /// <returns></returns>
    private int CountH(int x, int y)
    {
        //计算水平方向的步数
        int newX = x - endX;
        //求绝对值
        newX = newX > 0 ? newX : -newX;
        //计算垂直方向的步数
        int newY = y - endY;
        //求绝对值
        newY = newY > 0 ? newY : -newY;
        //返回乘10后的结果
        return 10 * (newX + newY);
    }

    /// <summary>
    /// 计算G值
    /// </summary>
    /// <param name="i"></param>
    /// <param name="j"></param>
    /// <returns></returns>
    private int CountOffsetG(int i, int j)
    {
        if (i == 0 || j == 0)
            return 10;
        return 14;
    }



    [Obsolete("这是麻烦的方法",true)]
    private int OldCountOffsetG(int i, int j)
    {
        return (int)Mathf.Sqrt(i * i + j * j) * 10 ;
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            UnityEngine.SceneManagement.SceneManager.LoadScene(0);
        }
    }
}